/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using Bitcoin.Cryptography;
using Bitcoin.IO;
using Org.BouncyCastle.Utilities.Encoders;

namespace Bitcoin
{
    /// <summary>
    ///     A transfer of coins from one address to another creates a transaction in which the outputs
    ///     can be claimed by the recipient in the input of another transaction. You can imagine a
    ///     transaction as being a module which is wired up to others, the inputs of one have to be wired
    ///     to the outputs of another. The exceptions are coinbase transactions, which create new coins.
    /// </summary>
    //[Serializable]
    public class TransactionInput
    {
        public enum ConnectionResult
        {
            NoSuchTx,
            AlreadySpent,
            Success
        }

        public static readonly byte[] EmptyArray = new byte[0];

        // Allows for altering transactions after they were broadcast. Tx replacement is currently disabled in the C++
        // client so this is always the UINT_MAX.
        // TODO: Document this in more detail and build features that use it.
        // The Script object obtained from parsing scriptBytes. Only filled in on demand and if the transaction is not
        // coinbase.
        /*[NonSerialized]*/
        private Script _scriptSig;
        private uint _sequence;

        /// <summary>
        ///     Used only in creation of the genesis block.
        /// </summary>
        internal TransactionInput(ChainParameters parameters, Transaction parentTransaction, byte[] scriptBytes)
        {
            ScriptBytes = scriptBytes;
            Outpoint = new TransactionOutPoint(-1, null);
            _sequence = uint.MaxValue;
            ParentTransaction = parentTransaction;
        }

        /// <summary>
        ///     Creates an UNSIGNED input that links to the given output
        /// </summary>
        internal TransactionInput(ChainParameters parameters, Transaction parentTransaction, TransactionOutput output)
        {
            var outputIndex = output.Index;
            Outpoint = new TransactionOutPoint(outputIndex, output.ParentTransaction);
            ScriptBytes = EmptyArray;
            _sequence = uint.MaxValue;
            ParentTransaction = parentTransaction;
        }

        public TransactionInput(byte[] outpointHashBytes, uint outpointIndex, byte[] scriptBytes, uint sequence)
        {
            this.Outpoint = new TransactionOutPoint(outpointHashBytes, outpointIndex);
            this.ScriptBytes = scriptBytes;
            _sequence = sequence;
        }

        /// <summary>
        ///     Convenience method that returns the from address of this input by parsing the scriptSig.
        /// </summary>
        /// <exception cref="ScriptException">If the scriptSig could not be understood (eg, if this is a coinbase transaction).</exception>
        //public BitcoinAddress FromAddress
        //{
        //    get
        //    {
        //        Debug.Assert(!IsCoinBase);
        //        return ScriptSig.FromAddress;
        //    }
        //}

        /// <summary>
        ///     Coinbase transactions have special inputs with hashes of zero. If this is such an input, returns true.
        /// </summary>
        public bool IsCoinBase
        {
            get { return Outpoint.Hash.Equals(Sha256Hash.ZeroHash); }
        }

        public TransactionOutPoint Outpoint { get; private set; }

        // A pointer to the transaction that owns this input.
        public Transaction ParentTransaction { get; set; }

        public byte[] ScriptBytes { get; set; }

        /// <summary>
        ///     Returns the input script.
        /// </summary>
        /// <exception cref="ScriptException" />
        public Script ScriptSig
        {
            get
            {
                // Transactions that generate new coins don't actually have a script. Instead this
                // parameter is overloaded to be something totally different.
                if (_scriptSig == null)
                {
                    Debug.Assert(ScriptBytes != null);
                    _scriptSig = new Script(null, ScriptBytes, 0, ScriptBytes.Length);
                }
                return _scriptSig;
            }
        }

        public uint Sequence
        {
            get { return _sequence; }
        }

        /// <summary>
        ///     Connects this input to the relevant output of the referenced transaction if it's in the given map.
        ///     Connecting means updating the internal pointers and spent flags.
        /// </summary>
        /// <param name="transactions">Map of txhash-&gt;transaction.</param>
        /// <param name="disconnect">Whether to abort if there's a pre-existing connection or not.</param>
        /// <returns>True if connection took place, false if the referenced transaction was not in the list.</returns>
        public ConnectionResult Connect(IDictionary<Sha256Hash, Transaction> transactions, bool disconnect)
        {
            Transaction tx;
            if (!transactions.TryGetValue(Outpoint.Hash, out tx))
                return ConnectionResult.NoSuchTx;
            var @out = tx.Outputs[Outpoint.Index];
            if (!@out.IsAvailableForSpending)
            {
                if (disconnect)
                    @out.MarkAsUnspent();
                else
                    return ConnectionResult.AlreadySpent;
            }
            Outpoint.FromTx = tx;
            @out.MarkAsSpent(this);
            return ConnectionResult.Success;
        }

        /// <summary>
        ///     Locates the referenced output from the given pool of transactions.
        /// </summary>
        /// <returns>The TransactionOutput or null if the transactions map doesn't contain the referenced tx.</returns>
        public TransactionOutput GetConnectedOutput(IDictionary<Sha256Hash, Transaction> transactions)
        {
            Transaction tx;
            if (!transactions.TryGetValue(Outpoint.Hash, out tx))
                return null;
            var @out = tx.Outputs[Outpoint.Index];
            return @out;
        }

        /// <summary>
        ///     Returns a human readable debug string.
        /// </summary>
        public override string ToString()
        {
            if (IsCoinBase)
            {
                return "TxIn: COINBASE";
            }
            return "TxIn from tx " + Outpoint + " (pubkey: " + Hex.ToHexString(ScriptSig.PubKey) +
                   ") script:" + ScriptSig;
        }

        /// <summary>
        ///     Release the connected output, making it spendable once again.
        /// </summary>
        /// <returns>True if the disconnection took place, false if it was not connected.</returns>
        internal bool Disconnect()
        {
            if (Outpoint.FromTx == null) return false;
            Outpoint.FromTx.Outputs[Outpoint.Index].MarkAsUnspent();
            Outpoint.FromTx = null;
            return true;
        }
    }
}